package com.zf.powerjob.worker.utils;

import com.cronutils.model.Cron;
import com.cronutils.model.definition.CronDefinition;
import com.cronutils.model.definition.CronDefinitionBuilder;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import lombok.extern.slf4j.Slf4j;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Objects;
import java.util.Optional;

/**
 * Cron表达式工具：和 PowerJob Server 使用的相同的依赖：
 * <pre>
 *     {@code
 *          <dependency>
 *             <groupId>com.cronutils</groupId>
 *             <artifactId>cron-utils</artifactId>
 *             <version>9.2.1</version>
 *         </dependency>
 *     }
 * </pre>
 *
 * @author Zhou Feng
 */
@Slf4j
public class CronUtils {

    private static final CronParser cronParser;

    static {
        CronDefinition cronDefinition = CronDefinitionBuilder.defineCron()
                .withSeconds().withValidRange(0, 59).and()
                .withMinutes().withValidRange(0, 59).and()
                .withHours().withValidRange(0, 23).and()
                .withDayOfMonth().withValidRange(1, 31).supportsL().supportsW().supportsLW().supportsQuestionMark().and()
                .withMonth().withValidRange(1, 12).and()
                .withDayOfWeek().withValidRange(1, 7).withMondayDoWValue(2).supportsHash().supportsL().supportsQuestionMark().and()
                .withYear().withValidRange(1970, 2099).withStrictRange().optional().and()
                .instance();
        cronParser = new CronParser(cronDefinition);
    }


    /**
     * 计算下次的调度时间
     *
     * @param preTriggerTime 上次触发时间的毫秒时间戳（初次触发填写当前时间）
     * @param timeExpression CRON表达式
     * @param startTime      起始时间，可以为 null, 为 null 则忽略该参数：
     *                       如果 [ startTime > preTriggerTime && startTime > 当前时间 ]，则计算出 距离 startTime 最近的上一次触发时间作为新的 preTriggerTime，然后再计算下次触发时间，
     *                       否则，忽略 startTime，直接使用传入的 preTriggerTime 来计算下次触发时间。
     * @param endTime        结束时间，可以为 null, 为 null 则忽略该参数：
     *                       如果 [ 计算出的下次触发时间 > endTime ]，则生成的下次触发时间超过了结束时间，是无效的，最终结果返回 null
     * @return 下次的调度时间(毫秒时间戳)
     */

    public static Long calculateNextTriggerTime(Long preTriggerTime, String timeExpression, Long startTime, Long endTime) {
        Cron cron = cronParser.parse(timeExpression);
        ExecutionTime executionTime = ExecutionTime.forCron(cron);
        if (startTime != null && startTime > System.currentTimeMillis() && preTriggerTime < startTime) {
            // 需要计算出离 startTime 最近的一次真正的触发时间
            Optional<ZonedDateTime> zonedDateTime = executionTime.lastExecution(ZonedDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneId.systemDefault()));
            preTriggerTime = zonedDateTime.map(dateTime -> dateTime.toEpochSecond() * 1000).orElse(startTime);
        }
        Instant instant = Instant.ofEpochMilli(preTriggerTime);
        ZonedDateTime preZonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
        Optional<ZonedDateTime> opt = executionTime.nextExecution(preZonedDateTime);
        if (opt.isPresent()) {
            long nextTriggerTime = opt.get().toEpochSecond() * 1000;
            if (endTime != null && endTime < nextTriggerTime) {
                return null;
            }
            return nextTriggerTime;
        }
        return null;
    }

    /**
     * 校验Cron表达式是否正确
     *
     * @param timeExpression cron表达式
     */
    public static boolean validateCronExpression(String timeExpression) {
        Cron cron = null;
        try {
            cron = cronParser.parse(timeExpression);
        } catch (IllegalArgumentException e) {
            log.error("Cron表达式错误：", e);
            return false;
        }
        return Objects.nonNull(cron);
    }

    public static void main(String[] args) {
        // 当前时间
        long currentTimeMillis = System.currentTimeMillis();

        // 3 天后
        long startTime = currentTimeMillis + 1000 * 60 * 60 * 24 * 3;


        // 10 天后
        long endTime = currentTimeMillis + 1000 * 60 * 60 * 24 * 10;


        Long nextTriggerTime1 = calculateNextTriggerTime(currentTimeMillis, "0 0 6 * * 6", null, null);
        System.out.println(nextTriggerTime1);


        Long nextTriggerTime2 = calculateNextTriggerTime(currentTimeMillis, "0 0 6 * * 6", startTime, endTime);
        System.out.println(nextTriggerTime2);
    }

}
